// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	11/19/2014 8:47:20 PM				
// -----------------------------

#include "Level.h"
#include "Camera.h"
#include "FreeCamera.h"
#include "InputManager.h"
#include "MeshActor.h"
#include "LightActor.h"

#include "Mesh.h"
#include "Plane.h"
#include "Cube.h"

#include "Material.h"
#include "DefaultMaterial.h"
#include "WaterMaterial.h"
#include "TerrainMaterial.h"

#include "Sound.h"

#include "TerrainGenerator.h"
#include "SkyboxGenerator.h"
#include "WaterDisplacementGenerator.h"
#include "TerrainTextureGenerator.h"
#include "SongGenerator.h"

#include "Application.h"
#include "RenderManager.h"
#include "RenderPass.h"
#include "SkyboxPass.h"
#include "PostPass.h"
#include "Window.h"

#include "Timer.h"

#include <iomanip>

Level::Level(const TString& Name)
	: Scene(Name)
	, m_WorldDimensions(5000.0f)
	, m_TerrainHeight(m_WorldDimensions * 0.6f)
	, m_WaterLevel(0.4f)
	, m_TextureDimensions(1024)
	, m_PlaneDimensions(32)
	, m_FogEnabled(true)
	, m_CameraSpeed(200.0f)
	, m_lpSkyboxTexture(nullptr)
	, m_lpFont(nullptr)
	, m_LoadTime(0.0f)
	, m_AverageFramerate(0)
	, m_FramerateTimer(0.0f)
	, m_SampleCount(0)
	, m_lpMusic(nullptr)
{
}

Level::~Level()
{
	NullRelease(&m_lpFont);

	delete m_lpMusic;
}

void Level::Load(Application* lpApp)
{
	Scene::Load(lpApp);

	// Create the Timer
	Timer* lpTimer = new Timer();
	lpTimer->Reset();
	lpTimer->Update();

	// Create the Font
	#ifndef _NOD2D
	m_lpFont = ITextFormat::Create(lpApp->GetRenderer()->GetDevice(), _T("Arial"), 14.0f);
	#endif

	// Setup the Camera
	FreeCamera* lpCamera = static_cast<FreeCamera*>(GetCamera());
	lpCamera->SetSpeed(m_CameraSpeed);
	lpCamera->SetPosition(Vector3(0.0f, m_TerrainHeight * 1.2f, 0.0f));
	lpCamera->SetLookAt(Vector3(0.0f, 0.0f, 0.0f));
	lpCamera->SetNear(100.0f);
	lpCamera->SetFar(m_WorldDimensions * 1.2f);

	// Create a Light
	LightActor* lpLight = new LightActor(_T("Sun"), LightActor::Directional);
	lpLight->SetPosition(Vector3(20.0f, 20.0f, -20.0f));
	lpLight->SetLookAt(Vector3(0.0f, 0.0f, 0.0f));
	AddActor(lpLight);

	#ifdef _DEBUG
	m_TextureDimensions = 256;
	#else
	m_TextureDimensions = 1024;
	#endif

	// Update the Post Pass
	PostPass* lpPostPass = static_cast<PostPass*>(lpApp->GetRenderer()->GetRenderPass("Post"));
	lpPostPass->SetWaterOverlayEnabled(false);
	lpPostPass->SetWaterOverlayColor(Color::FromRGBA(60, 60, 240, 255));
	lpPostPass->SetWaterOverlayStrength(0.3f);
	lpPostPass->SetWaterVignetteRadius(0.4f);
	lpPostPass->SetFogNear(lpCamera->GetFar() * 0.6f);
	lpPostPass->SetFogFar(lpCamera->GetFar());
	lpPostPass->SetFogColor(Color(0.45f, 0.45f, 0.45f));
	lpPostPass->SetFogSecondColor(Color(0.8f, 0.8f, 0.8f));
	lpPostPass->SetFogEnabled(m_FogEnabled);

	// Create the Elements
	Log::WriteLine(_T("[LOAD]\t\tGenerating Skybox Texture."));
	CreateSkyboxTexture(lpApp);
	Log::WriteLine(_T("[LOAD]\t\tGenerating Terrain."));
	CreateTerrain(lpApp);
	Log::WriteLine(_T("[LOAD]\t\tGenerating Water."));
	CreateWater(lpApp);
	Log::WriteLine(_T("[LOAD]\t\tGenerating Music."));
	CreateMusic(lpApp);

	// Update the Timer
	lpTimer->Update();
	m_LoadTime = lpTimer->GetDeltaTime();
	Log::WriteLine(_T("[LOAD]\t\tLevel Load Time: %3.2f seconds"), m_LoadTime);
	delete lpTimer;
}
void Level::OnActivate(Application* lpApp)
{
	if (m_lpMusic)
		m_lpMusic->Play(true, 1.0f);
}
void Level::OnDeactivate(Application* lpApp)
{
	if (m_lpMusic)
		m_lpMusic->Stop();
}
void Level::OnGUI(Application* lpApp, IRenderTarget2D* lpTarget)
{
	Scene::OnGUI(lpApp, lpTarget);

	Float halfWidth = lpApp->GetWindow()->GetWidth() / 2.0f;
	Float halfHeight = lpApp->GetWindow()->GetHeight() / 2.0f;
	Float radius = 1.5f;

	lpTarget->FillEllipse(halfWidth, halfHeight, radius, radius);

	Float deltaTime = lpApp->GetTimer()->GetDeltaTime();
	m_FramerateTimer += deltaTime;
	++m_SampleCount;
	if (m_FramerateTimer >= 1.0f)
	{
		m_AverageFramerate = m_SampleCount;
		m_SampleCount = 0;
		m_FramerateTimer = 0.0f;
	}

	TStringStream textStream;
	TStringStream numberStream;
	textStream << _T("Load:\n");
	numberStream << std::setprecision(2) << m_LoadTime << _T("s\n");
	textStream << _T("DT:\n");
	numberStream << std::setprecision(2) << deltaTime * 1000.0f << _T("ms\n");
	textStream << _T("FPS:\n");
	numberStream << (UInt32)(1.0f / deltaTime) << _T("\n");
	textStream << _T("AFPS:");
	numberStream << m_AverageFramerate;
	textStream << _T("\n\n");
	numberStream << _T("\n\n");
	textStream << _T("Speed:\n");
	numberStream << std::setprecision(5) << m_CameraSpeed;

	Float x = 5.0f;
	Float y = 5.0f;
	Float blockWidth = 45.0f;
	Float blockHeight = 100.0f;
	m_lpFont->SetTextAlignment(TextAlignment::Leading);
	lpTarget->DrawString(RectF(Vector2F(x, y), blockWidth, blockHeight), textStream.str().c_str(), textStream.str().length(), m_lpFont);
	m_lpFont->SetTextAlignment(TextAlignment::Trailing);
	lpTarget->DrawString(RectF(Vector2F(x + blockWidth, y), blockWidth, blockHeight), numberStream.str().c_str(), numberStream.str().length(), m_lpFont);
}

void Level::Update(Application* lpApp, Float deltaTime)
{
	Scene::Update(lpApp, deltaTime);

	PostPass* lpPostPass = static_cast<PostPass*>(lpApp->GetRenderer()->GetRenderPass("Post"));
	MeshActor* lpWater = static_cast<MeshActor*>(FindActor(_T("WaterActor")));
	WaterMaterial* lpWaterMaterial = static_cast<WaterMaterial*>(lpWater->GetMaterial());

	Vector3 camPos = GetCamera()->GetPosition();
	Float estimatedMinHeight = (m_TerrainHeight * m_WaterLevel) + 100.0f;
	// First check if we are within the Esimated Height Range
	Bool bInWater = false;
	if (camPos.y <= estimatedMinHeight) // Under the Minimum Value
		bInWater = true;
	else // Above
		bInWater = false;

	lpPostPass->SetWaterOverlayEnabled(bInWater);

	// Enable/Disable Fog
	if (lpApp->GetInput()->IsKeyPressed(VK_F3))
	{
		m_FogEnabled = !m_FogEnabled;
		lpPostPass->SetFogEnabled(m_FogEnabled);
	}

	// Increase/Decrease Camera Speed
	FreeCamera* lpCamera = static_cast<FreeCamera*>(GetCamera());
	if (lpApp->GetInput()->IsKeyDown(VK_ADD))
	{
		m_CameraSpeed += 100.0f * deltaTime;
		lpCamera->SetSpeed(m_CameraSpeed);
	}
	else if (lpApp->GetInput()->IsKeyDown(VK_SUBTRACT))
	{
		m_CameraSpeed -= 100.0f * deltaTime;
		m_CameraSpeed = Math::Max(m_CameraSpeed, 20.0f);
		lpCamera->SetSpeed(m_CameraSpeed);
	}
}

void Level::CreateTerrain(Application* lpApp)
{
	// Create a Terrain
	MeshActor* lpTerrainActor = new MeshActor(_T("TerrainActor"));

	TerrainDesc islandDesc = {};
	islandDesc.Subdivisions = m_TextureDimensions;
	islandDesc.Rougness = 0.21f;
	islandDesc.RoughnessFalloff = 0.47f;
	islandDesc.BaseOffset = 0.7f;

	// Generate the Height Data
	Float* lpHeightData = nullptr;
	RefPtr<ITexture2D> lpHeightTexture = RefPtr<ITexture2D>(TerrainGenerator::Generate(lpApp, islandDesc, &lpHeightData), true);

	// Generate a Plane
	Plane* lpTerrainMesh = new Plane(lpApp->GetRenderer(), m_WorldDimensions, m_WorldDimensions, m_PlaneDimensions, m_PlaneDimensions, true);

	TerrainMaterial* lpTerrainMaterial = new TerrainMaterial(lpApp);
	lpTerrainMaterial->m_Diffuse = Color::Grey;
	lpTerrainMaterial->m_Specular = 0.0f;
	lpTerrainMaterial->m_Roughness = 1.0f;
	lpTerrainMaterial->m_Ambient = Color::FromRGBA(64, 64, 64, 255);
	lpTerrainMaterial->m_MinimumHeight = 0.0f;
	lpTerrainMaterial->m_MaximumHeight = m_TerrainHeight;
	lpTerrainMaterial->m_MinimumDistance = 200.0f;
	lpTerrainMaterial->m_MaximumDistance = (m_WorldDimensions / 10.0f) * 3.0f;
	lpTerrainMaterial->m_MaximumTessFactor = 32.0f;
	lpTerrainMaterial->m_TerrainWidth = m_WorldDimensions;
	lpTerrainMaterial->m_TerrainHeight = m_WorldDimensions;

	TerrainTextureDesc texDesc = {};
	texDesc.Width = islandDesc.Subdivisions + 1;
	texDesc.Height = islandDesc.Subdivisions + 1;
	texDesc.BlurSamples = 3;
	texDesc.MinOffset = -0.1f;
	texDesc.MaxOffset = 0.1f;
	texDesc.FadeBorder = 0.05f;
	texDesc.HeightData = lpHeightData;
	texDesc.Elements =
	{
		{ Color::FromRGBA(235, 221, 156, 255), 0.00f },
		{ Color::FromRGBA(158, 109, 65, 255), m_WaterLevel + 0.05f },
		{ Color::FromRGBA(160, 160, 160, 255), 0.65f },
		{ Color::FromRGBA(250, 250, 250, 255), 0.95f },
	};

	RefPtr<ITexture2D> lpTerrainTex = RefPtr<ITexture2D>(TerrainTextureGenerator::Generate(lpApp, texDesc), true);
	lpTerrainMaterial->SetDiffuseTexture(lpTerrainTex);
	lpTerrainMaterial->SetHeightTexture(lpHeightTexture);

	lpTerrainActor->SetMesh(lpTerrainMesh);
	lpTerrainActor->SetMaterial(lpTerrainMaterial);

	lpTerrainMesh->Release();
	lpTerrainMaterial->Release();

	delete[] lpHeightData;

	AddActor(lpTerrainActor);
}
void Level::CreateWater(Application* lpApp)
{
	// Create the Water
	MeshActor* lpWaterActor = new MeshActor(_T("WaterActor"));
	lpWaterActor->SetPosition(Vector3(0.0f, m_TerrainHeight * m_WaterLevel, 0.0f));

	Mesh* lpWaterMesh = new Plane(lpApp->GetRenderer(), m_WorldDimensions, m_WorldDimensions, m_PlaneDimensions, m_PlaneDimensions, true);

	// Create the Material
	WaterMaterial* lpWaterMaterial = new WaterMaterial(lpApp);
	lpWaterMaterial->m_DiffuseColor = Color::FromRGBA(29, 46, 152, 255);
	lpWaterMaterial->m_AmbientColor = Color::FromRGBA(254, 252, 188, 255);
	lpWaterMaterial->m_SpecularColor = Color::FromRGBA(196, 196, 196, 255);
	lpWaterMaterial->m_Specular = 0.8f;
	lpWaterMaterial->m_SpecularPower = 12.0f;
	lpWaterMaterial->m_Opacity = 0.80f;
	lpWaterMaterial->m_WaveSpeed = 0.003f;
	lpWaterMaterial->m_Reflection = 0.4f;
	lpWaterMaterial->m_AmbientStrength = 0.2f;
	lpWaterMaterial->m_MinimumDistance = 200.0f;
	lpWaterMaterial->m_MaximumDistance = (m_WorldDimensions / 10.0f) * 3.0f;
	lpWaterMaterial->m_MaximumTessFactor = 16.0f;
	lpWaterMaterial->m_WaterWidth = m_WorldDimensions;
	lpWaterMaterial->m_WaterHeight = m_WorldDimensions;

	// Create the Water Displacement Texture
	RefPtr<ITexture2D> lpDisplacementTex = RefPtr<ITexture2D>(WaterDisplacementGenerator::Generate(lpApp, m_TextureDimensions, m_TextureDimensions, 200.0f), true);
	lpWaterMaterial->SetDisplacementTexture(lpDisplacementTex);

	// Set the Reflection Map
	lpWaterMaterial->SetReflectionMap(m_lpSkyboxTexture);

	lpWaterActor->SetMesh(lpWaterMesh);
	lpWaterActor->SetMaterial(lpWaterMaterial);

	lpWaterMesh->Release();
	lpWaterMaterial->Release();

	AddActor(lpWaterActor);
}
void Level::CreateSkyboxTexture(Application* lpApp)
{
	SkyboxDesc desc = {};
	desc.Width = m_TextureDimensions;
	desc.Height = m_TextureDimensions;
	desc.HorizonColor = Color::FromRGBA(221, 137, 45, 255);
	desc.SkyColor = Color::FromRGBA(174, 202, 249, 255);
	desc.CloudColor = Color::FromRGBA(240, 240, 240, 255);
	desc.HorizonStart = 0.4f;
	desc.HorizonEnd = 0.65f;
	desc.HorizonPower = 1;
	desc.CloudStart = 0.7f;
	desc.CloudEnd = 1.0f;
	desc.CloudThreshold = 0.4f;
	desc.CloudScale = 4.0f;
	desc.CloudBorder = 0.2f;

	m_lpSkyboxTexture = RefPtr<ITexture2D>(SkyboxGenerator::Generate(lpApp, desc), true);

	RenderPass* lpPass = lpApp->GetRenderer()->GetRenderPass("Skybox");
	SkyboxPass* lpSkybox = static_cast<SkyboxPass*>(lpPass);
	lpSkybox->SetTexture(m_lpSkyboxTexture);
}
void Level::CreateMusic(Application* lpApp)
{
	SongDesc desc = {};
	// Melody
	desc.Melody.Amplitude = 0.6f;
	desc.Melody.BaseFrequency = 220.0f;
	desc.Melody.MinimumNoteLength = 10;
	desc.Melody.MaximumNoteLength = 15;
	desc.Melody.MinimumNotes = 16;	
	desc.Melody.MaximumNotes = 24;
	desc.Melody.MinimumNoteOffset = 0;
	desc.Melody.MaximumNoteOffset = 6;
	desc.Melody.MinimumOverlap = 5;
	desc.Melody.MaximumOverlap = 10;
	desc.Melody.Type = SoundWaveType::Sawtooth;

	m_lpMusic = SongGenerator::Generate(lpApp, desc);
}